/* * Copyright 2003-2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.attributes.compiler; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.StringTokenizer; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; import com.thoughtworks.qdox.JavaDocBuilder; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaField; import com.thoughtworks.qdox.model.JavaMethod; import com.thoughtworks.qdox.model.DocletTag; import com.thoughtworks.qdox.model.JavaParameter; /** * Ant task to compile attributes. Usage: * * <pre><code> * <taskdef resource="org/apache/commons/attributes/anttasks.properties"/> * * <attribute-compiler destDir="temp/"> attributepackages="my.attributes;my.otherattributes" * <fileset dir="src/" includes="*.java"/> * </attribute-compiler> * </code></pre> * * <ul> * <li>destDir<dd>Destination directory for generated source files * <li>attributepackages<dd>A set of package names that will be automatically searched for attributes. * </ul> * * The task should be run before compiling the Java sources, and will produce some * additional Java source files in the destination directory that should be compiled * along with the input source files. (See the overview for a diagram.) */ public class AttributeCompiler extends Task { private final ArrayList fileSets = new ArrayList (); private Path src; private File destDir; private int numGenerated; private int numIgnored; private String attributePackages = ""; public AttributeCompiler () { } public void setAttributePackages (String attributePackages) { this.attributePackages = attributePackages; } public void addFileset (FileSet set) { fileSets.add (set); } public void setDestdir (File destDir) { this.destDir = destDir; } public void setSourcepathref (String pathref) { String sourcePaths = project.getReference (pathref).toString (); StringTokenizer tok = new StringTokenizer (sourcePaths, File.pathSeparator); while (tok.hasMoreTokens ()) { FileSet fs = new FileSet (); fs.setDir (new File (tok.nextToken ())); fs.setIncludes ("**/*.java"); fs.setProject (project); addFileset (fs); } } protected void copyImports (File source, PrintWriter dest) throws Exception { BufferedReader br = new BufferedReader (new FileReader (source)); try { String line = null; while ((line = br.readLine ()) != null) { if (line.startsWith ("import ")) { dest.println (line); } } } finally { br.close (); } } protected void addExpressions (DocletTag[] tags, PrintWriter pw, String collectionName, File sourceFile) { addExpressions (tags, null, pw, collectionName, sourceFile); } protected void addExpressions (DocletTag[] tags, String selector, PrintWriter pw, String collectionName, File sourceFile) { String fileName = sourceFile != null ? sourceFile.getPath ().replace ('\\', '/') : "<unknown>"; for (int i = 0; i < tags.length; i++) { DocletTag tag = tags[i]; if (isAttribute (tag)) { String tagName = tag.getName (); String tagValue = tag.getValue (); String expression = tagName + " " + tagValue; expression = expression.trim (); // Remove the second @-sign. expression = expression.substring (1); if (selector != null) { if (expression.startsWith (".")) { // We have selector, tag does... String tagSelector = expression.substring (1, expression.indexOf (" ")); expression = expression.substring (expression.indexOf (" ")).trim (); if (!selector.equals (tagSelector)) { // ...but they didn't match. continue; } } else { // We have selector, but tag doesn't continue; } } else { // No selector, but tag has selector. if (expression.startsWith (".")) { continue; } } pw.println (" {"); outputAttributeExpression (pw, expression, fileName, tag.getLineNumber (), "_attr"); pw.println (" Object _oattr = _attr; // Need to erase type information"); pw.println (" if (_oattr instanceof org.apache.commons.attributes.Sealable) {"); pw.println (" ((org.apache.commons.attributes.Sealable) _oattr).seal ();"); pw.println (" }"); pw.println (" " + collectionName + ".add ( _attr );"); pw.println (" }"); } } } protected void outputAttributeExpression (PrintWriter pw, String expression, String filename, int line, String tempVariableName) { AttributeExpressionParser.ParseResult result = AttributeExpressionParser.parse (expression, filename, line); pw.print (" " + result.className + " " + tempVariableName + " = new " + result.className + "("); boolean first = true; Iterator iter = result.arguments.iterator (); while (iter.hasNext ()) { AttributeExpressionParser.Argument arg = (AttributeExpressionParser.Argument) iter.next (); if (arg.field == null) { if (!first) { pw.print (", "); } first = false; pw.print (arg.text); } } pw.println (" // " + filename + ":" + line); pw.println (");"); iter = result.arguments.iterator (); while (iter.hasNext ()) { AttributeExpressionParser.Argument arg = (AttributeExpressionParser.Argument) iter.next (); if (arg.field != null) { String methodName = "set" + arg.field.substring (0, 1).toUpperCase () + arg.field.substring (1); pw.println (" " + tempVariableName + "." + methodName + "(\n" + arg.text + " // " + filename + ":" + line + "\n" + ");"); } } } protected boolean elementHasAttributes (JavaField[] fields) { for (int i = 0; i < fields.length; i++) { if (tagHasAttributes (fields[i].getTags ())) { return true; } } return false; } protected boolean elementHasAttributes (JavaMethod[] methods) { for (int i = 0; i < methods.length; i++) { if (tagHasAttributes (methods[i].getTags ())) { return true; } } return false; } /** * Encodes a class name to the internal Java name. * For example, an inner class Outer.Inner will be * encoded as Outer$Inner. */ private void getTransformedQualifiedName (JavaClass type, StringBuffer sb) { sb.append (type.getFullyQualifiedName ()); } protected String getParameterTypes (JavaParameter[] parameters) { StringBuffer sb = new StringBuffer (); for (int i = 0; i < parameters.length; i++) { if (i > 0) { sb.append (","); } getTransformedQualifiedName (parameters[i].getType ().getJavaClass (), sb); for (int j = 0; j < parameters[i].getType ().getDimensions (); j++) { sb.append ("[]"); } } return sb.toString (); } protected void generateClass (JavaClass javaClass) throws Exception { String name = null; File sourceFile = null; File destFile = null; String packageName = null; String className = null; packageName = javaClass.getPackage (); if (packageName == null) { packageName = ""; } if (javaClass.isInner ()) { sourceFile = getSourceFile (javaClass); String nonPackagePrefix = javaClass.getParent ().getClassNamePrefix (); if (packageName.length () > 0) { nonPackagePrefix = nonPackagePrefix.substring (packageName.length () + 1); } className = nonPackagePrefix + javaClass.getName (); name = javaClass.getParent ().getClassNamePrefix () + javaClass.getName (); } else { name = javaClass.getFullyQualifiedName (); sourceFile = getSourceFile (javaClass); className = javaClass.getName (); } if (sourceFile == null) { log ("Unable to find source file for: " + name); } destFile = new File (destDir, name.replace ('.', '/') + "$__attributeRepository.java"); /*if (javaClass.isAnonymous ()) { log (javaClass.getName () + " is anonymous - ignoring.", Project.MSG_VERBOSE); numIgnored++; return; }*/ if (!hasAttributes (javaClass)) { if (destFile.exists ()) { destFile.delete (); } return; } if (destFile.exists () && sourceFile != null && destFile.lastModified () >= sourceFile.lastModified ()) { return; } numGenerated++; destFile.getParentFile ().mkdirs (); PrintWriter pw = new PrintWriter (new FileWriter (destFile)); try { if (packageName != null && !packageName.equals ("")) { pw.println ("package " + packageName + ";"); } if (sourceFile != null) { copyImports (sourceFile, pw); } StringTokenizer tok = new StringTokenizer (attributePackages, ";"); while (tok.hasMoreTokens ()) { pw.println ("import " + tok.nextToken () + ".*;"); } pw.println ("public class " + className + "$__attributeRepository implements org.apache.commons.attributes.AttributeRepositoryClass {"); { pw.println (" private final java.util.Set classAttributes = new java.util.HashSet ();"); pw.println (" private final java.util.Map fieldAttributes = new java.util.HashMap ();"); pw.println (" private final java.util.Map methodAttributes = new java.util.HashMap ();"); pw.println (" private final java.util.Map constructorAttributes = new java.util.HashMap ();"); pw.println (); pw.println (" public " + className + "$__attributeRepository " + "() {"); pw.println (" initClassAttributes ();"); pw.println (" initMethodAttributes ();"); pw.println (" initFieldAttributes ();"); pw.println (" initConstructorAttributes ();"); pw.println (" }"); pw.println (); pw.println (" public java.util.Set getClassAttributes () { return classAttributes; }"); pw.println (" public java.util.Map getFieldAttributes () { return fieldAttributes; }"); pw.println (" public java.util.Map getConstructorAttributes () { return constructorAttributes; }"); pw.println (" public java.util.Map getMethodAttributes () { return methodAttributes; }"); pw.println (); pw.println (" private void initClassAttributes () {"); addExpressions (javaClass.getTags (), pw, "classAttributes", sourceFile); pw.println (" }"); pw.println (); // ---- Field Attributes pw.println (" private void initFieldAttributes () {"); pw.println (" java.util.Set attrs = null;"); JavaField[] fields = javaClass.getFields (); for (int i = 0; i < fields.length; i++) { JavaField member = (JavaField) fields[i]; if (member.getTags ().length > 0) { String key = member.getName (); pw.println (" attrs = new java.util.HashSet ();"); addExpressions (member.getTags (), pw, "attrs", sourceFile); pw.println (" fieldAttributes.put (\"" + key + "\", attrs);"); pw.println (" attrs = null;"); pw.println (); } } pw.println (" }"); // ---- Method Attributes pw.println (" private void initMethodAttributes () {"); pw.println (" java.util.Set attrs = null;"); pw.println (" java.util.List bundle = null;"); JavaMethod[] methods = javaClass.getMethods (); for (int i = 0; i < methods.length; i++) { JavaMethod member = (JavaMethod) methods[i]; if (!member.isConstructor () && member.getTags ().length > 0) { StringBuffer sb = new StringBuffer (); sb.append (member.getName ()).append ("("); sb.append (getParameterTypes (member.getParameters ())); sb.append (")"); String key = sb.toString (); pw.println (" bundle = new java.util.ArrayList ();"); pw.println (" attrs = new java.util.HashSet ();"); addExpressions (member.getTags (), null, pw, "attrs", sourceFile); pw.println (" bundle.add (attrs);"); pw.println (" attrs = null;"); pw.println (" attrs = new java.util.HashSet ();"); addExpressions (member.getTags (), "return", pw, "attrs", sourceFile); pw.println (" bundle.add (attrs);"); pw.println (" attrs = null;"); JavaParameter[] parameters = member.getParameters (); for (int j = 0; j < parameters.length; j++) { JavaParameter parameter = (JavaParameter) parameters[j]; pw.println (" attrs = new java.util.HashSet ();"); addExpressions (member.getTags (), parameter.getName (), pw, "attrs", sourceFile); pw.println (" bundle.add (attrs);"); pw.println (" attrs = null;"); } pw.println (" methodAttributes.put (\"" + key + "\", bundle);"); pw.println (" bundle = null;"); pw.println (); } } pw.println (" }"); // ---- Constructor Attributes pw.println (" private void initConstructorAttributes () {"); pw.println (" java.util.Set attrs = null;"); pw.println (" java.util.List bundle = null;"); JavaMethod[] constructors = javaClass.getMethods (); for (int i = 0; i < constructors.length; i++) { JavaMethod member = (JavaMethod) constructors[i]; if (member.isConstructor () && member.getTags ().length > 0) { StringBuffer sb = new StringBuffer (); sb.append ("("); sb.append (getParameterTypes (member.getParameters ())); sb.append (")"); String key = sb.toString (); pw.println (" bundle = new java.util.ArrayList ();"); pw.println (" attrs = new java.util.HashSet ();"); addExpressions (member.getTags (), null, pw, "attrs", sourceFile); pw.println (" bundle.add (attrs);"); pw.println (" attrs = null;"); JavaParameter[] parameters = member.getParameters (); for (int j = 0; j < parameters.length; j++) { JavaParameter parameter = (JavaParameter) parameters[j]; pw.println (" attrs = new java.util.HashSet ();"); addExpressions (member.getTags (), parameter.getName (), pw, "attrs", sourceFile); pw.println (" bundle.add (attrs);"); pw.println (" attrs = null;"); } pw.println (" constructorAttributes.put (\"" + key + "\", bundle);"); pw.println (" bundle = null;"); pw.println (); } } pw.println (" }"); } pw.println ("}"); pw.close (); } catch (Exception e) { pw.close (); destFile.delete (); throw e; } } /** * Finds the source file of a class. * * @param qualifiedName the fully qualified class name * @return the file the class is defined in. * @throws BuildException if the file could not be found. */ protected File getSourceFile (JavaClass javaClass) throws BuildException { return javaClass.getSource ().getFile (); } protected boolean hasAttributes (JavaClass javaClass) { if (tagHasAttributes (javaClass.getTags ()) || elementHasAttributes (javaClass.getFields ()) || elementHasAttributes (javaClass.getMethods ()) ) { return true; } return false; } /** * Tests if a tag is an attribute. Currently the test is * only "check if it is defined with two @-signs". */ protected boolean isAttribute (DocletTag tag) { return tag.getName ().length () > 0 && tag.getName ().charAt (0) == '@'; } public void execute () throws BuildException { destDir.mkdirs (); numGenerated = 0; try { JavaDocBuilder builder = new JavaDocBuilder (); for (int i = 0; i < fileSets.size (); i++) { FileSet fs = (FileSet) fileSets.get (i); DirectoryScanner ds = fs.getDirectoryScanner(project); File fromDir = fs.getDir(project); String[] srcFiles = ds.getIncludedFiles(); for (int j = 0; j < srcFiles.length; j++) { String srcName = srcFiles[j]; File sourceFile = new File (fromDir, srcName); builder.addSource (sourceFile); } } JavaClass[] classes = builder.getClasses (); for (int i = 0; i < classes.length; i++) { generateClassAndInners (classes[i]); } log ("Generated attribute information for " + numGenerated + " classes. Ignored " + numIgnored + " classes."); } catch (BuildException be) { throw be; } catch (Exception e) { e.printStackTrace (); throw new BuildException (e.toString (), e); } } public void generateClassAndInners (JavaClass clazz) throws Exception { generateClass (clazz); JavaClass[] classes = clazz.getInnerClasses (); for (int i = 0; i < classes.length; i++) { generateClassAndInners (classes[i]); } } /** * Checks if a collection of XTags contain any tags specifying attributes. */ protected boolean tagHasAttributes (DocletTag[] tags) { for (int i = 0; i < tags.length; i++) { if (isAttribute (tags[i])) { return true; } } return false; } }